package SSF.OS.WWW;

/**
 * httpServer.java
 *
 * Created: Sun Nov 28 1999
 * @author ato
 * @version 0.2
 */

import com.renesys.raceway.SSF.*;
import SSF.OS.*;
import SSF.OS.TCP.*;
import SSF.OS.Socket.*;
import com.renesys.raceway.DML.*;
import SSF.Net.*;
import SSF.Net.Util.*;
import SSF.Util.Random.*;

/** A simple HTTP server traffic generator.
 *  The server configures itself from the DML file. After receiving
 *  an httpRequest, the server spawns a slave server that first 
 *  sends an httpResponse, and then sends the virtual data to the client
 *  until the request is fulfilled, and possibly closes the TCP
 *  connection if the httpRequest contained the CLOSE directive.
 *  <P>The default is no limit on the number of clients, unless the attribute
 *  client_limit is set. <P>There
 *  is no limit on the number of servers that can coexist in a single Host
 *  as long as each httpServer has a distinct wellKnownPort number.
 */
public class httpServer extends ProtocolSession {

  /****************** Generic Server Variables *********************/

  /** port number used by this server */
  int wellKnownPort;

  /** nominal size of httpRequest/Response headers from/to httpClient */
  int httpHdrSize = 960;

  /** "listening" socket of this server */
  tcpSocket lsd;

  /** max number of concurrent clients of this server - default is unlimited */
  int client_limit = Integer.MAX_VALUE;

  /** The maximum size of pending connection request queue, Qlimit,
   *  that determines when the server socket rejects connection
   *  requests.
   *  @see SSF.OS.TCP.tcpSocket
   */
  int qlimit = 5;


  /** current number of client requests serviced by this server */
  int clientNumber = 0;

  /** Host entity where this pseudo-protocol is installed */
  Host localHost;

  /** Host's IP address */
  int localIP;

  /** Host's NHI address */
  String localNHI;

  /** Host's Socket ProtocolSession */
  socketMaster socketms;

  /** default is don't print verbose debugging information */
  boolean debug = false;

  /** default is show summary session information */
  boolean show_report = true;

  /****************** Server Statistical Model ******************/

  /** generator of number of objects per page */
  RandomDistribution objPerPageGen;

  /** generator of object sizes (in bytes) */
  RandomDistribution objSizeGen;

  /** generator of server's request-to-response delays, in seconds */
  RandomDistribution delayGen;

  /*************** The slave server inner class ***************/
 
  class slaveServer extends ProtocolSession {
    httpServer owner;
    httpRequest req;
    tcpSocket sd;

    public slaveServer(tcpSocket ssd) {
      owner = httpServer.this;
      sd = ssd;
    }

    Object[] inobj;
    Object[] outobj;
    int numObjects;
    int objSize;
    Continuation masterCont;

    void serviceRequest(Continuation caller) {
      masterCont = caller;
      inobj = new Object[1];
      sd.read(inobj, owner.httpHdrSize, new Continuation() {
        public void success() {
          if (debug)
            sessionDebug(0,((httpRequest)inobj[0]).toString() + " OK");
          req = (httpRequest)inobj[0];
          if(req.requestType == httpRequest.CLOSE) {
            close(0);
            return;
          }
          if((req.requestType & httpRequest.PAGE) == httpRequest.PAGE)
            numObjects = Math.max(1, owner.objPerPageGen.nextInt());
          else
            numObjects = 1;   // for req.requestType & httpRequest.OBJECT = httpRequest.OBJECT

          objSize = Math.max(1, owner.objSizeGen.nextInt());
          outobj = new Object[1];
          outobj[0] =  new httpResponse(req.requestType, numObjects, objSize);

          // first respond to client with object size to expect.
          // respond with the same header type as in received request
          // we should add response delay here - not done yet.

          sd.write(outobj, owner.httpHdrSize, new Continuation() {
            public void success() {
              // then send the virtual bytes representing the object
              sd.write(objSize, new Continuation() {
                public void success() {
                  // close connection if that was requested by client
                  if((req.requestType & httpRequest.CLOSE) == httpRequest.CLOSE)
                    close(0);
                  else
                    serviceRequest(masterCont);  // keep persistent connection
                }
                public void failure(int errno) {
                  if (debug)
                    sessionDebug(errno, "data write failure:");
                  masterCont.failure(errno);
                }
              });
            } 
            public void failure(int errno) {
              if (debug)
                sessionDebug(errno, "response write failure:");
              masterCont.failure(errno);
            }
          });
        }
        public void failure(int errno) { // failed reading clnt request
          if (debug)
            sessionDebug(errno, "read request failure:");
          close(errno);
          masterCont.failure(errno);
        }
      });
    }

    void close(int errno) {
      try {
        sd.close(new Continuation() {
          public void success() {
            if(owner.debug)
              serverInfo("httpServer close() OK");
          }
          public void failure(int errno) {
            if(owner.debug)
              sessionDebug(errno, "httpServer close() error");
          }
        });
      } catch (ProtocolException e) {
          System.err.println(e);
      }
      if(errno > 0)
        masterCont.failure(errno);
      else
        masterCont.success();
    }

    /** preamble to server-side diagnostics */
    public void sessionDebug(int errno, String str){

      StringBuffer lineBuffer = new StringBuffer(200);
      lineBuffer.append(localHost.now()/(double)SSF.Net.Net.seconds(1.0))
        .append(" "+owner.name+" ").append(localNHI).append(" ")
        .append(IP_s.IPtoString(localIP)).append(":").append(wellKnownPort)
        .append(" "+str);

      if(errno>0)
        lineBuffer.append(" "+socketMaster.errorString(errno));
      System.err.println(lineBuffer);
    }

    public boolean push(ProtocolMessage message, ProtocolSession fromSession)
      throws ProtocolException {
        return false;
    }
  } // end of slaveServer

  /************************* Constructors ************************/

  public httpServer() {}

  /************************ Class Methods ***********************/

  /** httpServer configuration. Example of supported DML attributes:
   * <PRE>
   *   ProtocolSession [ name server use SSF.OS.WWW.httpServer
   *     port 80            # server's HTTP port
   *
   *     client_limit  10     # max number of simultaneous established client
   *                          # connections, if omitted, default is unlimited.
   *                          # If exceeded, connection request is ignored
   *                          # and left to time out.
   *
   *     queue_limit   5      # maximum size of pending connection request
   *                          # queue, Qlimit, if omitted default is 5.
   *                          # Determines the total number of pending
   *                          # connection requests (see SSF.OS.TCP.tcpSocket).
   *                          # If exceeded, the listening socket sends RESET
   *                          # to client and drops the request.
   *
   *     # distribution of the number of objects in a page request:
   *     # if random variable = 0, round up to 1
   *     # NOTE: the response to page request contains the 1st object
   *
   *     objects_in_page [
   *       distribution [
   *         name "Pareto"              # distribution class name
   *         k 0.6667                   # scale (cutoff) parameter
   *         alpha 1.2                  # shape (exponent) parameter
   *       ]
   *     ]
   *
   *     # distribution of sizes of objects (in bytes)
   *     # if random variable = 0, round up to 1
   *
   *     object_size [
   *       distribution [
   *         name "Pareto"              # distribution class name
   *         k 2000.0                   # scale (cutoff) parameter
   *         alpha 1.2                  # shape (exponent) parameter
   *       ]
   *     ]
   *
   *     # distribution of server delays between request and response (in seconds)
   *     # NOT IMPLEMENTED
   *
   *     response_delay [
   *       distribution [
   *         name "Exponential"           # distribution class name
   *         lambda 10.0                  # mean = 1/lambda
   *       ]
   *     ]
   *
   *     http_hdr_size 1000  # nominal HTTP header size (virtual bytes) read from a socket
   *                         # before reading data (if indicated by http header)
   *
   *     show_report true    # print client-server session summary report
   *     debug false         # print verbose client/server diagnostics
   *
   *   ]
   * </PRE>
   */
  public void config(Configuration cfg) throws configException {
    super.config(cfg);
    String str;

    if((str = (String)cfg.findSingle("port")) != null) {
      wellKnownPort = (new Integer(str)).intValue();
      if (wellKnownPort > 10000)
        serverInfo("Warning: httpServer port must be < 10,000.");
    }

    if((str = (String)cfg.findSingle("client_limit")) != null)
      client_limit = (new Integer(str)).intValue();

    if((str = (String)cfg.findSingle("queue_limit")) != null)
      qlimit = (new Integer(str)).intValue();

    str = (String)cfg.findSingle("debug");
    if((str != null) && (str.compareTo("true") == 0))
      debug =  true;

    str = (String)cfg.findSingle("show_report");
    if((str != null) && (str.compareTo("false") == 0))
      show_report = false;

    str = (String)cfg.findSingle("http_hdr_size");
    if(str == null)
      throw new configException
        ("http_hdr_size not found");
    httpHdrSize = (new Integer(str)).intValue();

    // now configure the distributions

    Configuration scfg = (Configuration)cfg.findSingle("objects_in_page");
    if(scfg == null)
      throw new configException
        ("objects_in_page not found");
    scfg = (Configuration)scfg.findSingle("distribution");
    if(scfg == null)
      throw new configException
        ("objects_in_page.distribution not found");

    objPerPageGen = Net.accessRandomDistribution(this, scfg, "objects_in_page"); 

    scfg = (Configuration)cfg.findSingle("object_size");
    if(scfg == null)
      throw new configException
        ("object_size not found");
    scfg = (Configuration)scfg.findSingle("distribution");
    if(scfg == null)
      throw new configException
        ("object_size.distribution not found");

    objSizeGen = Net.accessRandomDistribution(this, scfg, "object_size"); 

    scfg = (Configuration)cfg.findSingle("response_delay");
    if(scfg == null)
      throw new configException
        ("response_delay not found");
    scfg = (Configuration)scfg.findSingle("distribution");
    if(scfg == null)
      throw new configException
        ("response_delay.distribution not found");

    delayGen = Net.accessRandomDistribution(this, scfg, "response_delay"); 
  }

  Object[] obj;

  /** Start the server */
  public void init(){
    localHost = (SSF.Net.Host)inGraph();
    localNHI = localHost.nhi;
    try {
      socketms = (socketMaster)localHost.SessionForName("socket");
      lsd = (tcpSocket)socketms.socket(this, "tcp");
      localIP = ((NIC)((IP)localHost.SessionForName("ip"))
                   .INTERFACES.elementAt(0)).ipAddr;
    }catch (ProtocolException e) {
      System.err.println("httpServer: " + e);
    } 
    lsd.bind(localIP, wellKnownPort);
    lsd.listen(client_limit);
    obj = new Object[1];
    serv();
  }

  socketAPI[] new_socket;

  public void serv() {
    new_socket = new socketAPI[1];
    try {
      lsd.accept(new_socket, new Continuation() {     // signal from socket
        public void success() {
          if(++clientNumber > client_limit) {
            if(debug)
              serverInfo("max # clients exceeded, refused");
            --clientNumber;
          } else {
            slaveServer ss = new slaveServer((tcpSocket)new_socket[0]);
            // register ss in socketMaster
            ss.serviceRequest(new Continuation() {   // signal from slaveServer
              public void success() {
                // tcp connection closed
                --clientNumber;
                if(debug)
                  serverInfo("serviceRequest() OK");
              }
              public void failure(int errno) {
                // client request servicing failed, clean up
                if(debug)
                  serverInfo("serviceRequest() FAILURE");
                --clientNumber;
              }
            });
          }
          serv();
        }
        public void failure(int errno) {
          // client connection request failed before servicing 
          serverInfo("accept() FAILURE");
          serv();
        }
      });
    } catch (ProtocolException e) {
      System.err.println(e);
    } 
  }  

  /** preamble to server diagnostics */
  void serverInfo(String str){
    System.out.println(localHost.now()/(double)SSF.Net.Net.seconds(1.0)
            + " httpServer " + localNHI + " " 
            + IP_s.IPtoString(localIP) + ":" + wellKnownPort + " " + str);
  }

  public boolean push(ProtocolMessage message, ProtocolSession fromSession)
    throws ProtocolException {
    return false;
  }
}
